/* Copyright (C) 2010 Luca Piccinelli
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* 
 * File:   serialization.h
 * Author: picci
 *
 * Created on July 29, 2011, 6:26 PM
 */

#include "pipeline/serialization.h"
#include "exceptions/serialization_exceptions.h"
#include "factory/factories.h"

#include <xercesc/framework/LocalFileFormatTarget.hpp>
#include <boost/lexical_cast.hpp>
#include <sstream>

#define LOAD_ERROR_PREFIX filename << ":Error while loading the pipestep descriptor: " 

#define X(str) XStr(str).unicodeForm()
#define S(x_str) std::string(x_str ? XMLString::transcode(x_str) : "")

#define GET_ATTR(from, to, attr, idx) \
        std::string to = S(from->getAttribute(X(attr))); \
        if(to == "") { \
            ss << LOAD_ERROR_PREFIX << "unspecified " << attr << " attribute for " << #from << " in pipeline " << idx; \
            throw bad_xml_node(ss.str()); \
        }

#define GET_NODE_ATTR(from, to, attr, idx) \
        attributes = from->getAttributes(); \
        if(!attributes){ \
            ss << LOAD_ERROR_PREFIX << "invalid xml tag for " << #from << " " << idx << " (it is not a DOMElement)"; \
            throw bad_xml_node(ss.str()); \
        } \
        node_attr = attributes->getNamedItem(X(attr)); \
        if(!node_attr) { \
            ss << LOAD_ERROR_PREFIX << "unspecified " << attr << " attribute for " << #from << " " << idx; \
            throw bad_xml_node(ss.str()); \
        } \
        std::string to = S(node_attr->getNodeValue()); \

using namespace xercesc;

// ---------------------------------------------------------------------------
//  This is a simple class that lets us do easy (though not terribly efficient)
//  trancoding of char* data to XMLCh data.
// ---------------------------------------------------------------------------
class XStr
{
public :
    // -----------------------------------------------------------------------
    //  Constructors and Destructor
    // -----------------------------------------------------------------------
    XStr(const char* const toTranscode)
    {
        // Call the private transcoding method
        fUnicodeForm = XMLString::transcode(toTranscode);
    }
    
    XStr(const std::string& toTranscode)
    {
        // Call the private transcoding method
        fUnicodeForm = XMLString::transcode(toTranscode.c_str());
    }

    ~XStr()
    {
        XMLString::release(&fUnicodeForm);
    }


    // -----------------------------------------------------------------------
    //  Getter methods
    // -----------------------------------------------------------------------
    const XMLCh* unicodeForm() const
    {
        return fUnicodeForm;
    }

private :
    // -----------------------------------------------------------------------
    //  Private data members
    //
    //  fUnicodeForm
    //      This is the Unicode XMLCh format of the string.
    // -----------------------------------------------------------------------
    XMLCh*   fUnicodeForm;
};

namespace NAMESPACE {

// ****** PipeStepXMLSerializer implementation *********************************    

PipeStepXMLSerializer* PipeStepXMLSerializer::instance = 0;
    
const std::string PipeStepXMLSerializer::NS                   = "pit";
const std::string PipeStepXMLSerializer::PIPELINES_COLLECTION = NS + ":" + "pipelines";
const std::string PipeStepXMLSerializer::PIPELINE             = NS + ":" + "pipeline";
const std::string PipeStepXMLSerializer::PIPESTEP             = NS + ":" + "pipestep";
const std::string PipeStepXMLSerializer::GENERATORS           = NS + ":" + "generators";
const std::string PipeStepXMLSerializer::GENERATOR            = NS + ":" + "generator";
const std::string PipeStepXMLSerializer::IN_GENERATORS        = NS + ":" + "input-generators";
const std::string PipeStepXMLSerializer::OUT_GENERATORS       = NS + ":" + "output-generators";
const std::string PipeStepXMLSerializer::CRITERIA             = NS + ":" + "criteria";
const std::string PipeStepXMLSerializer::CRITERION            = NS + ":" + "criterion";
const std::string PipeStepXMLSerializer::FORMATTERS           = NS + ":" + "formatters";
const std::string PipeStepXMLSerializer::PARAMETER            = NS + ":" + "parameter";

PipeStepXMLSerializer::~PipeStepXMLSerializer(){
    doc->release();
    XMLPlatformUtils::Terminate();
}

PipeStepXMLSerializer::PipeStepXMLSerializer() : impl(0), doc(0) {
    PipeStepXMLSerializer::PipeStepXMLSerializer("LS");
}

PipeStepXMLSerializer::PipeStepXMLSerializer(const PipeStepXMLSerializer& psx) {}

PipeStepXMLSerializer::PipeStepXMLSerializer(const std::string& s) : AbsPipeStepDescrConsumer() {
    XMLPlatformUtils::Initialize();    
    impl = DOMImplementationRegistry::getDOMImplementation(X(s));    
    doc = impl->createDocument(X(NS), X(PIPELINES_COLLECTION), 0);
}


std::string param_to_string(Configuration::params_map_t::const_iterator& param){
    std::stringstream ss;
    std::string type = param->first.getType();
    
    if     ( type == TypeParseTraits<int>::name())           ss << boost::any_cast<int>(*(param->second));
    else if( type == TypeParseTraits<double>::name())        ss << boost::any_cast<double>(*(param->second));
    else if( type == TypeParseTraits<float>::name())         ss << boost::any_cast<float>(*(param->second));
    else if( type == TypeParseTraits<memory_size_t>::name()) ss << boost::any_cast<memory_size_t>(*(param->second));
    else if( type == TypeParseTraits<long>::name())          ss << boost::any_cast<long>(*(param->second));
    else if( type == TypeParseTraits<std::string>::name())   ss << boost::any_cast<std::string>(*(param->second));
    else if( type == TypeParseTraits<IOKey>::name()){
        IOKey k = boost::any_cast<IOKey>(*(param->second));
        ss << k.get_key() << Key::KEY_SEPARATOR << k.getType();
    }
    else{
        ss << "it is not possible to persist a parameter of type " << param->first.getType();
        throw bad_parameter(ss.str());
    }
    
    return ss.str();
}
    
void PipeStepXMLSerializer::consume_descriptor(const PipeStepDescriptor& d, DOMElement* root){
    const PipelineDescriptor& pd = dynamic_cast<const PipelineDescriptor&>(d);
    
    DOMElement* pipeline = doc->createElementNS(X(NS), X(PIPELINE));
    pipeline->setAttribute( X("id"), X(pd.get_id()) );
    root->appendChild(pipeline);
    
    const std::vector<PipeStepDescriptor*>& steps_descr = pd.get_computation_steps();
    std::vector<PipeStepDescriptor*>::const_iterator steps_it     = steps_descr.begin(),
                                                     steps_end_it = steps_descr.end();
    for(; steps_it != steps_end_it; steps_it++){
        DOMElement* step = doc->createElementNS(X(NS), X(PIPESTEP));
        step->setAttribute( X("id"), X((*steps_it)->get_id()) );
        
        const SingleStepDescriptor* ssd = dynamic_cast<const SingleStepDescriptor*>(*steps_it);
        if(ssd)
            step->setAttribute( X("implementor"),  X(ssd->get_computation_name()) );
        else
            consume_descriptor(**steps_it, step);
                
        pipeline->appendChild(step);
        
        // Sections: generators, criteria, formatters
        DOMElement* generators_section = doc->createElementNS(X(NS), X(GENERATORS));
        step->appendChild(generators_section);
        DOMElement* criteria_section = doc->createElementNS(X(NS), X(CRITERIA));
        step->appendChild(criteria_section);
        DOMElement* formatters_section = doc->createElementNS(X(NS), X(FORMATTERS));
        step->appendChild(formatters_section);
        
        // sub-sections: generators subs
        DOMElement* in_generators_section = doc->createElementNS(X(NS), X(IN_GENERATORS));
        generators_section->appendChild(in_generators_section);
        DOMElement* out_generators_section = doc->createElementNS(X(NS), X(OUT_GENERATORS));
        generators_section->appendChild(out_generators_section);
        
        // Criteria section data
        std::map<IOKey, std::string>::const_iterator crit_it     = (*steps_it)->get_all_criteria().begin(),
                                                     end_crit_it = (*steps_it)->get_all_criteria().end();
        
        DOMElement* criterion = 0;
        DOMText* criterion_data = 0;
        for(; crit_it != end_crit_it; crit_it++){
            criterion = doc->createElementNS(X(NS), X(CRITERION));
            criterion->setAttribute( X("key"),  X(crit_it->first.get_key()) );
            criterion->setAttribute( X("type"), X(crit_it->first.getType()) );
            criterion_data = doc->createTextNode(X(crit_it->second));
            criterion->appendChild(criterion_data);
            criteria_section->appendChild(criterion);
        }
        
        // Input generators section data
        std::map<IOKey, std::pair<std::string, Configuration> >::const_iterator gen_it     = (*steps_it)->get_all_in_generators().begin(),
                                                                                end_gen_it = (*steps_it)->get_all_in_generators().end();
        Configuration::params_map_t::const_iterator param_it, param_end_it;
        
        DOMElement* gen = 0;
        DOMElement* param = 0;
        DOMText* param_data = 0;
        for(; gen_it != end_gen_it; gen_it++){
            gen = doc->createElementNS(X(NS), X(GENERATOR));
            gen->setAttribute( X("key"),  X(gen_it->first.get_key()) );
            gen->setAttribute( X("type"), X(gen_it->first.getType()) );
            gen->setAttribute( X("implementor"),  X(gen_it->second.first) );
            
            param_it     = gen_it->second.second.get_all_params().begin();
            param_end_it = gen_it->second.second.get_all_params().end();
            for(;param_it != param_end_it; param_it++){
                param = doc->createElementNS(X(NS), X(PARAMETER));
                param->setAttribute( X("key"),  X(param_it->first.get_key()) );
                param->setAttribute( X("type"), X(param_it->first.getType()) );
                param_data = doc->createTextNode(X(param_to_string(param_it)));   
                
                param->appendChild(param_data);
                gen->appendChild(param);
            }
            in_generators_section->appendChild(gen);
        }
        
        // Output generators section data
        gen_it     = (*steps_it)->get_all_out_generators().begin();
        end_gen_it = (*steps_it)->get_all_out_generators().end();
        
        for(; gen_it != end_gen_it; gen_it++){
            gen = doc->createElementNS(X(NS), X(GENERATOR));
            gen->setAttribute( X("key"),  X(gen_it->first.get_key()) );
            gen->setAttribute( X("type"), X(gen_it->first.getType()) );
            gen->setAttribute( X("implementor"),  X(gen_it->second.first) );
            
            param_it     = gen_it->second.second.get_all_params().begin();
            param_end_it = gen_it->second.second.get_all_params().end();
            for(;param_it != param_end_it; param_it++){
                param = doc->createElementNS(X(NS), X(PARAMETER));
                param->setAttribute( X("key"),  X(param_it->first.get_key()) );
                param->setAttribute( X("type"), X(param_it->first.getType()) );
                param_data = doc->createTextNode(X(param_to_string(param_it)));   
                
                param->appendChild(param_data);
                gen->appendChild(param);
            }
            out_generators_section->appendChild(gen);
        }
        
    }
}

void PipeStepXMLSerializer::consume(const PipeStepDescriptor& d){    
    DOMElement* root = doc->getDocumentElement();
    consume_descriptor(d, root);
}
    
bool PipeStepXMLSerializer::serialize(const std::string& filename) {
    DOMLSSerializer* serializer = ((DOMImplementationLS*)impl)->createLSSerializer();
    if(serializer->getDomConfig()->canSetParameter(XMLUni::fgDOMWRTFormatPrettyPrint, true))
        serializer->getDomConfig()->setParameter(XMLUni::fgDOMWRTFormatPrettyPrint, true);
    
    XMLFormatTarget* format_target = new LocalFileFormatTarget(X(filename));
    DOMLSOutput* out = ((DOMImplementationLS*)impl)->createLSOutput();
    
    out->setByteStream(format_target);
    bool result = serializer->write(doc, out);
    
    doc->release();
    doc = impl->createDocument(X(NS), X(PIPELINES_COLLECTION), 0);
    
    out->release();
    serializer->release();
    delete format_target;
    
    return result;
}

PipeStepXMLSerializer* PipeStepXMLSerializer::get_instance(const std::string& s){
    if(!instance) instance = new PipeStepXMLSerializer(s);
    return instance;
}
void PipeStepXMLSerializer::free_instance(){
    if(instance){
        delete instance;
        instance = 0;
    }
}
// -----------------------------------------------------------------------------
    



// ****** XMLPipeStepDescriptorBuilder implementation **************************

XMLPipeStepDescriptorBuilder* XMLPipeStepDescriptorBuilder::instance = 0;

XMLPipeStepDescriptorBuilder::XMLPipeStepDescriptorBuilder() : AbsPipeStepDescrBuilder() {
    XMLPipeStepDescriptorBuilder::XMLPipeStepDescriptorBuilder("", "LS");
}

XMLPipeStepDescriptorBuilder::XMLPipeStepDescriptorBuilder(const std::string& _filename, const std::string& s) : AbsPipeStepDescrBuilder(),
                                                                                                                 filename(_filename) {
    XMLPlatformUtils::Initialize();    
    impl = DOMImplementationRegistry::getDOMImplementation(X(s));
    parser = ((DOMImplementationLS*)impl)->createLSParser(DOMImplementationLS::MODE_SYNCHRONOUS, 0);
}

XMLPipeStepDescriptorBuilder::XMLPipeStepDescriptorBuilder(const XMLPipeStepDescriptorBuilder&){}

XMLPipeStepDescriptorBuilder::~XMLPipeStepDescriptorBuilder(){
    parser->release();
    XMLPlatformUtils::Terminate();
}
    
void XMLPipeStepDescriptorBuilder::configuration_from_DOM(DOMNodeList* params, int idx, Configuration& cfg){
    std::stringstream ss;
    DOMNamedNodeMap* attributes = 0;
    DOMNode* node_attr = 0;
    
    GET_NODE_ATTR(params->item(idx), param_key,  "key", idx)
    GET_NODE_ATTR(params->item(idx), param_type, "type", idx)
    std::string param_value = S(params->item(idx)->getTextContent());
    
    IOKey key;
    key.set_key(param_key);
    key.setType(param_type);
    
    try{
        if     ( param_type == TypeParseTraits<int>::name())           cfg.set_parameter(key, boost::lexical_cast<int>(param_value));
        else if( param_type == TypeParseTraits<double>::name())        cfg.set_parameter(key, boost::lexical_cast<double>(param_value));
        else if( param_type == TypeParseTraits<float>::name())         cfg.set_parameter(key, boost::lexical_cast<float>(param_value));
        else if( param_type == TypeParseTraits<memory_size_t>::name()) cfg.set_parameter(key, boost::lexical_cast<memory_size_t>(param_value));
        else if( param_type == TypeParseTraits<long>::name())          cfg.set_parameter(key, boost::lexical_cast<long>(param_value));
        else if( param_type == TypeParseTraits<std::string>::name())   cfg.set_parameter(key, param_value);
        else if( param_type == TypeParseTraits<IOKey>::name()){
            IOKey k;
            k.set_key_and_type(param_value);
            cfg.set_parameter(key, k);
        }
        else{
            ss << LOAD_ERROR_PREFIX << "unknown parameter of type " << param_type << " and value " << param_value;
            throw bad_parameter(ss.str());
        }
    }catch(boost::bad_lexical_cast& ex){
        ss << LOAD_ERROR_PREFIX << "the " << idx << " parameter value " << param_value << " is incompatible with the specified type " << ex.target_type().name();
        throw bad_xml_value(ss.str());
    }
}

void XMLPipeStepDescriptorBuilder::parse_generators(DOMElement* elem, SingleStepDescriptor& ssd){
    if (!elem) return;
    
    std::stringstream ss;
    DOMNamedNodeMap* attributes = 0;
    DOMNode* node_attr = 0;
    DOMNodeList* generators = elem->getElementsByTagName(X(PipeStepXMLSerializer::GENERATOR));
            
    for(int j = 0; generators && j < generators->getLength(); j++){
        DOMNodeList* gen_params = dynamic_cast<DOMElement*>(generators->item(j))->getElementsByTagName(X(PipeStepXMLSerializer::PARAMETER));
        GET_NODE_ATTR(generators->item(j), gen_implementor, "implementor", j)
        GET_NODE_ATTR(generators->item(j), key_name, "key", j)
        GET_NODE_ATTR(generators->item(j), key_type, "type", j)
        IOKey key;
        key.set_key(key_name);
        key.setType(key_type);
        Configuration cfg(FactoriesPool<Generator>::get_instance()->get_configuration_template(gen_implementor));
        // PARAMETERS ITERATION
        for(int k = 0; k < gen_params->getLength(); k++){
            configuration_from_DOM(gen_params, k, cfg);
        }
        if(S(elem->getNodeName()) == PipeStepXMLSerializer::IN_GENERATORS)
             ssd.set_in_conf(key, gen_implementor, cfg);
        else if(S(elem->getNodeName()) == PipeStepXMLSerializer::OUT_GENERATORS)
            ssd.set_out_conf(key, gen_implementor, cfg);
        else{
            ss << LOAD_ERROR_PREFIX                    << 
                  "Invalid xml tag "                   << 
                  S(elem->getNodeName())               << 
                  "; should be one of "                << 
                  PipeStepXMLSerializer::IN_GENERATORS << 
                  " or "                               << 
                  PipeStepXMLSerializer::OUT_GENERATORS;
            throw bad_xml_node(ss.str());
        }
    }
}

void XMLPipeStepDescriptorBuilder::build(std::vector<PipeStepDescriptor*>& descriptors){
    std::stringstream ss;
    if(filename == "") throw serialization_exception("Error while loading the pipestep descriptor: no filename set");
    
    DOMDocument* doc = parser->parseURI(X(filename));
    
    DOMNodeList* pipelines = doc->getElementsByTagName(X(PipeStepXMLSerializer::PIPELINE));
    if(pipelines->getLength() == 0) {
        ss << "Error while loading the pipestep descriptor: no pipelines defined";
        throw bad_xml_node(ss.str());
    }
    
    PipelineDescriptor* pd = 0;
    SingleStepDescriptor* ssd = 0;
    
    DOMNode* pipestep_node = 0;
    DOMElement* pipestep = 0;
    
    DOMNamedNodeMap* attributes = 0;
    DOMNode* node_attr = 0;
    
    // PIPELINES ITERATION
    for(int i = 0; i < pipelines->getLength(); i++){
        GET_NODE_ATTR(pipelines->item(i), id_str, "id", i)
        
        pd = new PipelineDescriptor();
        pd->set_id(id_str);
        
        DOMNodeList* pipesteps = dynamic_cast<DOMElement*>(pipelines->item(i))->getElementsByTagName(X(PipeStepXMLSerializer::PIPESTEP));
        
        // PIPESTEPS ITERATION
        for(int h = 0; h < pipesteps->getLength(); h++){
            pipestep_node = pipesteps->item(h);
            pipestep = dynamic_cast<DOMElement*>(pipestep_node);
            
            GET_ATTR(pipestep, step_id, "id", i)
            GET_ATTR(pipestep, step_computation_name, "implementor", i)
            SingleStepDescriptor ssd(step_id);
            ssd.set_computation(step_computation_name);
            
            // INPUT GENERATORS ITERATION
            DOMElement* in_gen_elem = pipestep->getFirstElementChild()->getFirstElementChild();
            parse_generators(in_gen_elem, ssd);
            
            // OUTPUT GENERATORS ITERATION
            DOMElement* out_gen_elem = in_gen_elem->getNextElementSibling();
            parse_generators(out_gen_elem, ssd);
            
            // CRITERIA ITERATION
            DOMNodeList* criteria = pipestep->getElementsByTagName(X(PipeStepXMLSerializer::CRITERION));
            
            for(int j = 0; criteria && j < criteria->getLength(); j ++){
                GET_NODE_ATTR(criteria->item(j), key_str,  "key",  j)
                GET_NODE_ATTR(criteria->item(j), key_type, "type", j)
                
                IOKey k;
                k.set_key(key_str);
                k.setType(key_type);
                ssd.set_criteria(k, S(criteria->item(j)->getTextContent()));
            }
            pd->add_step(ssd);
        }
        
        descriptors.push_back(pd);
    }
}
    
XMLPipeStepDescriptorBuilder* XMLPipeStepDescriptorBuilder::get_instance(const std::string& _filename, const std::string& s){
    if(!instance) instance = new XMLPipeStepDescriptorBuilder(_filename, s);
    return instance;
}
void XMLPipeStepDescriptorBuilder::free_instance(){
    if(instance){
        delete instance;
        instance = 0;
    }
}
    
// -----------------------------------------------------------------------------    

}